package de.herberlin.helpsystem;

import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.net.URL;
import java.util.Hashtable;
import java.util.LinkedList;

import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTree;
import javax.swing.UIManager;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.text.DefaultEditorKit;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreePath;

/**
 * Main entry point for helpsystem.
 *
 * This is the GUI Class that hold a JTree (displaying
 * hierarchy) and an JEditorPane (for HTML.)
 *
 * @author Hans Joachim Herbertz
 */
public class SimpleHelpSystem
	extends JPanel
	implements TreeSelectionListener, HyperlinkListener, Runnable {

	/**
	 * The name of the property given with a property change event.
	 */
	public static final String CURRENT_DISPLAYED_URL = "currentUrl";

	/**
	 * Tree for hierarchy */
	private JTree tree = null;
	/**
	 * Displays HTML. */
	private JEditorPane browser = null;

	/**
	 * Displays URL with of EditPane Hyperlink-Events. */
	private JLabel urlDisplay = new JLabel(" ");
	/**
	 * Displays html title.*/
	private JLabel titleLabel = new JLabel(" ");

	/**
	 * Panel at the top right position for Buttons.
	 * Added to the top position of main with getRightPanel(). */
	private JPanel topButtonPanel = new JPanel(new FlowLayout());

	/**
	 * History Back implementation.*/
	private LinkedList history = new LinkedList();

	public SimpleHelpSystem() {
		init("help");
	}
	/**
	 * Constructor.
	 * Creates GUI. Help system directory is parsed here
	 * for file hirarchy. The parser for Title runs in another
	 * thread. */
	public SimpleHelpSystem(String subdirectory) {
		init(subdirectory);
	}

	/**
	 * GUI initialization:*/
	private void init(String helplocationName) {

		setLayout(new BorderLayout());

		// Main Layout
		JSplitPane split = new JSplitPane();
		split.setLeftComponent(getLeftPanel(helplocationName));
		split.setRightComponent(getRightPanel());
		split.setOneTouchExpandable(true);
		split.setResizeWeight(0);
		add(split, BorderLayout.CENTER);

		// topPanel
		JPanel topPanel = new JPanel(new BorderLayout());
		topPanel.add(titleLabel, BorderLayout.CENTER);
		topPanel.add(topButtonPanel, BorderLayout.EAST);
		add(topPanel, BorderLayout.NORTH);

		display("/");

		Thread thread = new Thread(this);
		thread.setPriority(Thread.MIN_PRIORITY);
		thread.start();
	}

	/**
	 * Keeps popups for the different components.*/
	private Hashtable popupMap = new Hashtable();
	/**
	 * Shows popup for a given event.
	 * Popups are stored to popupMap according to the
	 * event source.*/
	private void showPopup(MouseEvent e) {
		if (e.isPopupTrigger()) {
			JComponent c = (JComponent) e.getSource();
			JPopupMenu p = (JPopupMenu) popupMap.get(e.getSource());
			if (p != null) {
				c.requestFocus();
				p.show(c, e.getX(), e.getY());
				e.consume();
			}
		}
	}

	private JPanel getLeftPanel(String helplocationName) {
		JPanel pLeft = new JPanel(new BorderLayout());
		TreeRoot node = null;
		try {
			node = new TreeRoot(helplocationName);
		} catch (HelpSystemException e) {
			showError(e);
			return pLeft;
		}
		tree = new JTree(node);

		ActionMap actionMap = tree.getActionMap();
		Action action = null;
		JPopupMenu popup = new JPopupMenu();

		JButton button = null;

		// Home Action
		action = actionMap.get("selectFirst");
		action.putValue(
			Action.SMALL_ICON,
			new ImageIcon(getClass().getResource("/pic/Home16.gif")));
		action.putValue(Action.SHORT_DESCRIPTION, "Home");
		action.putValue(Action.NAME, "Home");
		System.out.println(System.getProperty("java.version"));
		System.out.println(action);
		System.out.println(action.getValue(Action.SMALL_ICON));
		System.out.println(action.getValue(Action.NAME));
		System.out.println(action.getValue(Action.SHORT_DESCRIPTION));
		button = new JButton(action);
		button.setBorder(BorderFactory.createEmptyBorder());
		button.setText("");
		button.setName("Home");
		popup.add(action);
		topButtonPanel.add(button);

		// SelectParent Action
		action = actionMap.get("selectParent");
		action.putValue(
			Action.SMALL_ICON,
			new ImageIcon(getClass().getResource("/pic/Up16.gif")));
		action.putValue(Action.SHORT_DESCRIPTION, "Parent");
		action.putValue(Action.NAME, "Parent");
		button = new JButton(action);
		button.setBorder(BorderFactory.createEmptyBorder());
		button.setText("");
		popup.add(action);
		topButtonPanel.add(button);

		// History Back
		try {
			action =
				DefinedAction
					.getAction(DefinedAction.HISTORY_BACK, new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					if (history.size() > 0) {
						history.removeLast();
						String location = (String) history.removeLast();
						if (location != null) {
							display(location);
						}
					}
				}
			});
		} catch (RuntimeException e2) {
			e2.printStackTrace();
		}
		popup.add(action);
		button = new JButton(action);
		button.setBorder(BorderFactory.createEmptyBorder());
		button.setText("");
		topButtonPanel.add(button);

		// Select Next
		action = actionMap.get("selectNext");
		action.putValue(
			Action.SMALL_ICON,
			new ImageIcon(getClass().getResource("/pic/Forward16.gif")));
		action.putValue(Action.SHORT_DESCRIPTION, "Next");
		action.putValue(Action.NAME, "Next");
		button = new JButton(action);
		button.setBorder(BorderFactory.createEmptyBorder());
		button.setText("");
		popup.add(action);
		topButtonPanel.add(button);

		// Copy
		action =
			DefinedAction
				.getAction(DefinedAction.COPY_TREE_TEXT, new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				try {
					Clipboard clip =
						Toolkit.getDefaultToolkit().getSystemClipboard();
					StringSelection sl = new StringSelection(titleLabel.getText());
					clip.setContents(sl, sl);
				} catch (Exception e1) {
					e1.printStackTrace();
				}
			}
		});
		popup.add(action);

		tree.addTreeSelectionListener(this);
		tree.setRootVisible(true);
		tree.setExpandsSelectedPaths(true);
		tree.setScrollsOnExpand(true);

		popupMap.put(tree, popup);
		tree.addMouseListener(new MouseAdapter() {
			public void mousePressed(MouseEvent e) {
				showPopup(e);
			}
			public void mouseReleased(MouseEvent e) {
				showPopup(e);
			}
		});

		pLeft.add(new JScrollPane(tree), BorderLayout.CENTER);

		return pLeft;
	}
	/**
	 * Builds the browsers panel and actions therefore.
	 */
	private JPanel getRightPanel() {
		JPanel pRight = new JPanel(new BorderLayout());
		browser = new JEditorPane();
		browser.setEditable(false);
		browser.addHyperlinkListener(this);
		pRight.add(new JScrollPane(browser), BorderLayout.CENTER);
		pRight.add(urlDisplay, BorderLayout.SOUTH);

		JPopupMenu popup = new JPopupMenu();
		ActionMap browserActions = browser.getActionMap();
		Action action = null;
		JButton button = null;

		// Copy Action
		action = browserActions.get(DefaultEditorKit.copyAction);
		action.putValue(
			Action.SMALL_ICON,
			new ImageIcon(getClass().getResource("/pic/Copy16.gif")));
		action.putValue(Action.SHORT_DESCRIPTION, "Copy selected text");
		button = new JButton(action);
		button.setBorder(BorderFactory.createEmptyBorder());
		button.setText("");
		// browserButtonPanel.add(button);
		popup.add(action);

		// Select all
		action = browserActions.get(DefaultEditorKit.selectAllAction);
		action.putValue(
			Action.SMALL_ICON,
			new ImageIcon(getClass().getResource("/pic/AlignJustify16.gif")));
		action.putValue(Action.SHORT_DESCRIPTION, action.getValue(Action.NAME));
		popup.add(action);

		// Copy Link location
		// this action must be disabled if no
		// hyperlink is selected
		copyLinkLocationAction =
			DefinedAction
				.getAction(
					DefinedAction.COPY_LINK_LOCATION,
					new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				copyLinkLocation();
			}
		});
		popup.add(copyLinkLocationAction);

		/**/
		popupMap.put(browser, popup);
		browser.addMouseListener(new MouseAdapter() {
			public void mousePressed(MouseEvent e) {
				showPopup(e);
			}
			public void mouseReleased(MouseEvent e) {
				showPopup(e);
			}
		});

		return pRight;
	}
	/**
	 * Copies current selected link to the clipboard.*/
	private void copyLinkLocation() {
		try {
			StringSelection selection =
				new StringSelection(urlDisplay.getText());
			Toolkit.getDefaultToolkit().getSystemClipboard().setContents(
				selection,
				selection);
		} catch (Throwable t) {
			showError(t);
		}
	}

	/**
	 * Opens message dialog to show the error.
	 * @param e
	 */
	private void showError(Throwable e) {
		e.printStackTrace();
		if (e.getMessage() != null) {
			JOptionPane.showMessageDialog(
				this,
				e.getMessage(),
				e.getClass().getName(),
				JOptionPane.ERROR_MESSAGE);
		}
	}
	/**
	 * Thread does actions that take a longer time
	 * eg. parsing titles. */
	public void run() {
		((TreeRoot) tree.getModel().getRoot()).parseTitles();
		// updates ui
		tree.setCellRenderer(new DefaultTreeCellRenderer());
	}

	/**
	 * TreeSelectionListener displays HTML page in JEditorPane if
	 * tree selection changes.
	 * @see javax.swing.event.TreeSelectionListener#valueChanged(TreeSelectionEvent)
	 */
	public void valueChanged(TreeSelectionEvent e) {
		TreePath path = e.getPath();
		tree.expandPath(path);
		tree.scrollPathToVisible(path);
		Object obj =
			((DefaultMutableTreeNode) path.getLastPathComponent())
				.getUserObject();
		if (obj instanceof TreeNodeObject) {

			try {
				flagDisableTreeCallback = true;
				display(TreeRoot.urlToIndex(((TreeNodeObject) obj).getUrl()));
				flagDisableTreeCallback = false;
			} catch (Throwable ex) {
				showError(ex);

			}
		}
	}

	/**
	 * Menu action copy link location.
	 * This action should be disabled if no html hyperlink
	 * is selected in browser.*/
	private Action copyLinkLocationAction = null;

	/**
	 * HyperlinkListener for EditorPane; synchronizes EditorPane
	 * and hirachical tree if a link is clicked.
	 * @see javax.swing.event.HyperlinkListener#hyperlinkUpdate(HyperlinkEvent)
	 */
	public void hyperlinkUpdate(HyperlinkEvent e) {
		if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
			try {
				// Is helppage called
				if (e
					.getURL()
					.getPath()
					.startsWith(TreeRoot.root.toURL().getPath())
					|| "jar".equals(e.getURL().getProtocol())) {

					display(TreeRoot.urlToIndex(e.getURL()));

				} else if (e.getURL() != null) {
					// FIXME
					// This is not stable and does not
					// show any processing infos
					browser.setPage(e.getURL());
				}
			} catch (Throwable ex) {
				showError(ex);
			}
		} else if (e.getEventType() == HyperlinkEvent.EventType.ENTERED) {
			urlDisplay.setText(e.getURL().toExternalForm());
			copyLinkLocationAction.setEnabled(true);
		} else if (e.getEventType() == HyperlinkEvent.EventType.EXITED) {
			urlDisplay.setText(" ");
			copyLinkLocationAction.setEnabled(false);
		}
	}

	/**
	 * Avoid TreeSelectionEvent calling display(..) again
	 * when Tree ist updated from another component. */
	private boolean flagDisableTreeCallback = false;

	/**
	 * Method displays a helppage with a given indexName and
	 * synchronizes HelpTree. Use this as the main entry point
	 * for your application.
	 * @param indexName Index name is the basic filename of the
	 * page to display, eg. if the filename is :
	 * /helpsystem.root/directory/file.html the indexName will be
	 * /directory/file
	 * @param anchor the html anchor to move to. May be null
	 * @throws HelpSystemException
	 */
	public void display(String indexName) {

		URL oldPage = browser.getPage();

		// Have a new String for the History for
		// indexName might be changed
		String myHistoryEntry = new String(indexName);
		Object obj = tree.getModel().getRoot();
		if (obj instanceof TreeRoot) {
			TreeRoot treeRoot = (TreeRoot) obj;

			// split anchor form indexName
			int pos = indexName.indexOf("#");
			String anchor = null;
			if (pos > 0) {
				anchor = indexName.substring(pos + 1);
				indexName = indexName.substring(0, pos);
			}

			DefaultMutableTreeNode node = treeRoot.getChildForIndex(indexName);
			if (indexName.equals("/"))
				node = treeRoot;

			if (node != null) {
				try {
					// Avoid TreeSelectionEvent to call this method again
					if (flagDisableTreeCallback == false) {
						tree.setSelectionPath(new TreePath(node.getPath()));
					}
					TreeNodeObject nodeObj =
						(TreeNodeObject) node.getUserObject();
					browser.setPage(nodeObj.getUrl());

					String title = nodeObj.getTitle();
					if (title == null) {
						title = nodeObj.getUrlAsFile().getName();
					}
					titleLabel.setText(title);
					if (anchor != null) {
						browser.scrollToReference(anchor);
					}

					// history add
					if (history.size() < 1) {
						history.add(myHistoryEntry);
					} else if (!myHistoryEntry.equals(history.getLast())) {
						history.add(myHistoryEntry);
					}
					// size of history is defined here
					if (history.size() > 30)
						history.removeFirst();
				} catch (Throwable e) {
					showError(e);
				}
			}
		}

		// fire the property change if displayed page changed
		if (!browser.getPage().equals(oldPage)) {
			firePropertyChange(
				CURRENT_DISPLAYED_URL,
				oldPage,
				browser.getPage());
		}
	}

	/**
	 * Main for standalone. */
	public static void main(String[] args) {

		System.out.println(Version.VERSION);
//		try {
//			UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
//			UIManager.setLookAndFeel(
//				"com.incors.plaf.kunststoff.KunststoffLookAndFeel");
//		} catch (Throwable e) {
//			e.printStackTrace();
//		}

		JFrame frame = new JFrame(Version.VERSION);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.getContentPane().add(new SimpleHelpSystem("help"));
		frame.pack();
		frame.setVisible(true);
	}
}
